home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / rhythmbox / plugins / magnatune / MagnatuneSource.py < prev    next >
Encoding:
Python Source  |  2009-04-07  |  20.8 KB  |  666 lines

  1. # -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
  2. #
  3. # Copyright (C) 2006 Adam Zimmerman  <adam_zimmerman@sfu.ca>
  4. # Copyright (C) 2006 James Livingston  <doclivingston@gmail.com>
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2, or (at your option)
  9. # any later version.
  10. #
  11. # The Rhythmbox authors hereby grant permission for non-GPL compatible
  12. # GStreamer plugins to be used and distributed together with GStreamer
  13. # and Rhythmbox. This permission is above and beyond the permissions granted
  14. # by the GPL license by which Rhythmbox is covered. If you modify this code
  15. # you may extend this exception to your version of the code, but you are not
  16. # obligated to do so. If you do not wish to do so, delete this exception
  17. # statement from your version.
  18. #
  19. # This program is distributed in the hope that it will be useful,
  20. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22. # GNU General Public License for more details.
  23. #
  24. # You should have received a copy of the GNU General Public License
  25. # along with this program; if not, write to the Free Software
  26. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  27.  
  28. import rb, rhythmdb
  29. from TrackListHandler import TrackListHandler
  30. from BuyAlbumHandler import BuyAlbumHandler, MagnatunePurchaseError
  31.  
  32. import os
  33. import gobject
  34. import gtk.glade
  35. import gnome, gconf
  36. import xml
  37. import urllib
  38. import urlparse
  39. import zipfile
  40.  
  41. has_gnome_keyring = False
  42.  
  43. #try:
  44. #    import gnomekeyring
  45. #    has_gnome_keyring = True
  46. #except:
  47. #    pass
  48.  
  49.  
  50. magnatune_partner_id = "ubuntu"
  51.  
  52. # URIs
  53. magnatune_song_info_uri = "http://magnatune.com/info/song_info_xml.zip"
  54.  
  55. magnatune_in_progress_dir = os.path.join(rb.user_data_dir(), 'magnatune')
  56. magnatune_cache_dir = os.path.join(rb.user_cache_dir(), 'magnatune')
  57.  
  58. magnatune_song_info = os.path.join(magnatune_cache_dir, 'song_info.xml')
  59. magnatune_song_info_temp = os.path.join(magnatune_cache_dir, 'song_info.zip.tmp')
  60.  
  61.  
  62. ALBUM_ART_URL = 'http://www.magnatune.com/music/%s/%s/cover.jpg'
  63.  
  64. class MagnatuneSource(rb.BrowserSource):
  65.     __gproperties__ = {
  66.         'plugin': (rb.Plugin, 'plugin', 'plugin', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY),
  67.     }
  68.  
  69.     __client = gconf.client_get_default()
  70.  
  71.  
  72.     def __init__(self):
  73.  
  74.         rb.BrowserSource.__init__(self, name=_("Magnatune"))
  75.         self.__db = None
  76.  
  77.         # track data
  78.         self.__sku_dict = {}
  79.         self.__home_dict = {}
  80.         self.__buy_dict = {}
  81.         self.__art_dict = {}
  82.  
  83.         # catalogue stuff
  84.         self.__activated = False
  85.         self.__notify_id = 0
  86.         self.__update_id = 0
  87.         self.__catalogue_loader = None
  88.         self.__catalogue_check = None
  89.         self.__info_screen = None
  90.         self.__updating = True
  91.         self.__has_loaded = False
  92.         self.__load_current_size = 0
  93.         self.__load_total_size = 0
  94.  
  95.         self.__downloads = {} # keeps track of amount downloaded for each file
  96.         self.__downloading = False # keeps track of whether we are currently downloading an album
  97.         self.__download_progress = 0.0 # progress of current download(s)
  98.         self.purchase_filesize = 0 # total amount of bytes to download
  99.  
  100.  
  101.  
  102.     def do_set_property(self, property, value):
  103.         if property.name == 'plugin':
  104.             self.__plugin = value
  105.         else:
  106.             raise AttributeError, 'unknown property %s' % property.name
  107.  
  108.     #
  109.     # RBSource methods
  110.     #
  111.  
  112.     def do_impl_show_entry_popup(self):
  113.         self.show_source_popup ("/MagnatuneSourceViewPopup")
  114.  
  115.     def do_impl_get_status(self):
  116.         if self.__updating:
  117.             if self.__load_total_size > 0:
  118.                 progress = min (float(self.__load_current_size) / self.__load_total_size, 1.0)
  119.             else:
  120.                 progress = -1.0
  121.             return (_("Loading Magnatune catalogue"), None, progress)
  122.         elif self.__downloading:
  123.             progress = min (self.__download_progress, 1.0)
  124.             return (_("Downloading Magnatune Album(s)"), None, progress)
  125.         else:
  126.             qm = self.get_property("query-model")
  127.             return (qm.compute_status_normal("%d song", "%d songs"), None, 0.0)
  128.  
  129.     def do_impl_get_ui_actions(self):
  130.         return ["MagnatunePurchaseAlbum",
  131.             "MagnatunePurchaseCD",
  132.             "MagnatuneArtistInfo",
  133.             "MagnatuneCancelDownload"]
  134.  
  135.     def do_impl_activate(self):
  136.         if not self.__activated:
  137.             shell = self.get_property('shell')
  138.             self.__db = shell.get_property('db')
  139.             self.__entry_type = self.get_property('entry-type')
  140.  
  141.             # move files from old ~/.gnome2 paths
  142.             if os.path.exists(magnatune_in_progress_dir) is False:
  143.                 self.__move_data_files()
  144.  
  145.             self.__activated = True
  146.             self.__show_loading_screen (True)
  147.  
  148.             # start our catalogue updates
  149.             self.__update_id = gobject.timeout_add(6 * 60 * 60 * 1000, self.__update_catalogue)
  150.             self.__update_catalogue()
  151.  
  152.             self.get_entry_view().set_sorting_type(self.__client.get_string("/apps/rhythmbox/plugins/magnatune/sorting"))
  153.  
  154.         rb.BrowserSource.do_impl_activate (self)
  155.  
  156.     def do_impl_get_browser_key (self):
  157.         return "/apps/rhythmbox/plugins/magnatune/show_browser"
  158.  
  159.     def do_impl_get_paned_key (self):
  160.         return "/apps/rhythmbox/plugins/magnatune/paned_position"
  161.  
  162.     def do_impl_pack_paned (self, paned):
  163.         self.__paned_box = gtk.VBox(False, 5)
  164.         self.pack_start(self.__paned_box)
  165.         self.__paned_box.pack_start(paned)
  166.  
  167.  
  168.     def do_impl_delete_thyself(self):
  169.         if self.__update_id != 0:
  170.             gobject.source_remove (self.__update_id)
  171.             self.__update_id = 0
  172.  
  173.         if self.__notify_id != 0:
  174.             gobject.source_remove (self.__notify_id)
  175.             self.__notify_id = 0
  176.  
  177.         if self.__catalogue_loader is not None:
  178.             self.__catalogue_loader.cancel()
  179.             self.__catalogue_loader = None
  180.  
  181.         if self.__catalogue_check is not None:
  182.             self.__catalogue_check.cancel()
  183.             self.__catalogue_check = None
  184.  
  185.         self.__client.set_string("/apps/rhythmbox/plugins/magnatune/sorting", self.get_entry_view().get_sorting_type())
  186.  
  187.         rb.BrowserSource.do_impl_delete_thyself (self)
  188.  
  189.     #
  190.     # methods for use by plugin and UI
  191.     #
  192.  
  193.     def display_artist_info(self):
  194.         tracks = self.get_entry_view().get_selected_entries()
  195.         urls = set([])
  196.  
  197.         for tr in tracks:
  198.             sku = self.__sku_dict[self.__db.entry_get(tr, rhythmdb.PROP_LOCATION)]
  199.             url = self.__home_dict[sku]
  200.             if url not in urls:
  201.                 rb.show_uri(url)
  202.                 urls.add(url)
  203.  
  204.     def buy_cd(self):
  205.         tracks = self.get_entry_view().get_selected_entries()
  206.         urls = set([])
  207.  
  208.         for tr in tracks:
  209.             sku = self.__sku_dict[self.__db.entry_get(tr, rhythmdb.PROP_LOCATION)]
  210.             url = self.__buy_dict[sku]
  211.             if url not in urls:
  212.                 rb.show_uri(url)
  213.                 urls.add(url)
  214.  
  215.     def radio_toggled(self, gladexml):
  216.         gc = gladexml.get_widget("radio_gc").get_active()
  217.         gladexml.get_widget("remember_cc_details").set_sensitive(not gc)
  218.         gladexml.get_widget("name_entry").set_sensitive(not gc)
  219.         gladexml.get_widget("cc_entry").set_sensitive(not gc)
  220.         gladexml.get_widget("mm_entry").set_sensitive(not gc)
  221.         gladexml.get_widget("yy_entry").set_sensitive(not gc)
  222.         
  223.         gladexml.get_widget("gc_entry").set_sensitive(gc)
  224.         if not gc:
  225.             gladexml.get_widget("gc_entry").set_text("")
  226.     
  227.     def purchase_album(self):
  228.         try:
  229.             library_location = self.__client.get_list("/apps/rhythmbox/library_locations", gconf.VALUE_STRING)[0] # Just use the first library location
  230.         except IndexError, e:
  231.             rb.error_dialog(title = _("Couldn't purchase album"),
  232.                         message = _("You must have a library location set to purchase an album."))
  233.             return
  234.  
  235.         tracks = self.get_entry_view().get_selected_entries()
  236.         skus = []
  237.  
  238.         for track in tracks:
  239.             sku = self.__sku_dict[self.__db.entry_get(track, rhythmdb.PROP_LOCATION)]
  240.             if sku in skus:
  241.                 continue
  242.             skus.append(sku)
  243.             artist = self.__db.entry_get(track, rhythmdb.PROP_ARTIST)
  244.             album = self.__db.entry_get(track, rhythmdb.PROP_ALBUM)
  245.  
  246.             gladexml = gtk.glade.XML(self.__plugin.find_file("magnatune-purchase.glade"))
  247.             cb_dict = {"rb_magnatune_on_radio_cc_toggled_cb":lambda w:self.radio_toggled(gladexml)}
  248.             gladexml.signal_autoconnect(cb_dict)
  249.             
  250.             gladexml.get_widget("gc_entry").set_sensitive(False)
  251.             gladexml.get_widget("pay_combobox").set_active(self.__client.get_int(self.__plugin.gconf_keys['pay']) - 5)
  252.             gladexml.get_widget("audio_combobox").set_active(self.__plugin.format_list.index(self.__client.get_string(self.__plugin.gconf_keys['format'])))
  253.             gladexml.get_widget("info_label").set_markup(_("Would you like to purchase the album <i>%(album)s</i> by '%(artist)s'?") % {"album":album, "artist":artist})
  254.             gladexml.get_widget("remember_cc_details").props.visible = has_gnome_keyring
  255.  
  256.             try:
  257.                 (ccnumber, ccyear, ccmonth, name, email) = self.__plugin.get_cc_details()
  258.                 gladexml.get_widget("cc_entry").set_text(ccnumber)
  259.                 gladexml.get_widget("yy_entry").set_text(ccyear)
  260.                 gladexml.get_widget("mm_entry").set_active(ccmonth-1)
  261.                 gladexml.get_widget("name_entry").set_text(name)
  262.                 gladexml.get_widget("email_entry").set_text(email)
  263.  
  264.                 gladexml.get_widget("remember_cc_details").set_active(True)
  265.             except Exception, e:
  266.                 print e
  267.  
  268.                 gladexml.get_widget("cc_entry").set_text("")
  269.                 gladexml.get_widget("yy_entry").set_text("")
  270.                 gladexml.get_widget("mm_entry").set_active(0)
  271.                 gladexml.get_widget("name_entry").set_text("")
  272.                 gladexml.get_widget("email_entry").set_text("")
  273.  
  274.                 gladexml.get_widget("remember_cc_details").set_active(False)
  275.  
  276.             window = gladexml.get_widget("purchase_dialog")
  277.             if window.run() == gtk.RESPONSE_ACCEPT:
  278.                 amount = gladexml.get_widget("pay_combobox").get_active() + 5
  279.                 format = self.__plugin.format_list[gladexml.get_widget("audio_combobox").get_active()]
  280.                 ccnumber = gladexml.get_widget("cc_entry").get_text()
  281.                 ccyear = gladexml.get_widget("yy_entry").get_text()
  282.                 ccmonth = str(gladexml.get_widget("mm_entry").get_active() + 1).zfill(2)
  283.                 name = gladexml.get_widget("name_entry").get_text()
  284.                 email = gladexml.get_widget("email_entry").get_text()
  285.                 gc = gladexml.get_widget("radio_gc").get_active()
  286.                 gc_text = gladexml.get_widget("gc_entry").get_text()
  287.  
  288.                 if gladexml.get_widget("remember_cc_details").props.active:
  289.                     self.__plugin.store_cc_details(ccnumber, ccyear, ccmonth, name, email)
  290.                 else:
  291.                     self.__plugin.clear_cc_details()
  292.  
  293.                 self.__buy_album (sku, amount, format, ccnumber, ccyear, ccmonth, name, email, gc, gc_text)
  294.  
  295.             window.destroy()
  296.  
  297.     #
  298.     # internal catalogue downloading and loading
  299.     #
  300.  
  301.     def __catalogue_chunk_cb(self, result, total, parser):
  302.         if not result or isinstance (result, Exception):
  303.             if result:
  304.                 # report error somehow?
  305.                 print "error loading catalogue: %s" % result
  306.  
  307.             try:
  308.                 parser.close ()
  309.             except xml.sax.SAXParseException, e:
  310.                 # there isn't much we can do here
  311.                 print "error parsing catalogue: %s" % e
  312.  
  313.             self.__show_loading_screen (False)
  314.             self.__updating = False
  315.             self.__catalogue_loader = None
  316.  
  317.             # restart in-progress downloads
  318.             # (doesn't really belong here)
  319.             inprogress = os.listdir(magnatune_in_progress_dir)
  320.             inprogress = filter(lambda x: x.startswith("in_progress_"), inprogress)
  321.             for ip in inprogress:
  322.                 for uri in open(ip).readlines():
  323.                     print "restarting download from %s" % uri
  324.                     self.__download_album(uri)
  325.  
  326.         else:
  327.             # hack around some weird chars that show up in the catalogue for some reason
  328.             result = result.replace("\x19", "'")
  329.             result = result.replace("\x13", "-")
  330.  
  331.             try:
  332.                 parser.feed(result)
  333.             except xml.sax.SAXParseException, e:
  334.                 print "error parsing catalogue: %s" % e
  335.  
  336.             self.__load_current_size += len(result)
  337.             self.__load_total_size = total
  338.  
  339.         self.__notify_status_changed()
  340.  
  341.  
  342.     def __load_catalogue(self):
  343.         self.__notify_status_changed()
  344.         self.__has_loaded = True
  345.  
  346.         parser = xml.sax.make_parser()
  347.         parser.setContentHandler(TrackListHandler(self.__db, self.__entry_type, self.__sku_dict, self.__home_dict, self.__buy_dict, self.__art_dict))
  348.         
  349.         self.__catalogue_loader = rb.ChunkLoader()
  350.         self.__catalogue_loader.get_url_chunks(magnatune_song_info, 64*1024, True, self.__catalogue_chunk_cb, parser)
  351.  
  352.  
  353.  
  354.     def __find_song_info(self, catalogue):
  355.         for info in catalogue.infolist():
  356.             if info.filename.endswith("song_info.xml"):
  357.                 return info.filename;
  358.         return None
  359.  
  360.  
  361.     def __download_catalogue_chunk_cb (self, result, total, out):
  362.         if not result:
  363.             # done downloading, unzip to real location
  364.             out.close()
  365.  
  366.             catalog = zipfile.ZipFile(magnatune_song_info_temp)
  367.             out = open(magnatune_song_info, 'w')
  368.             filename = self.__find_song_info(catalog)
  369.             if filename is None:
  370.                 rb.error_dialog(title=_("Unable to load catalogue"),
  371.                         message=_("Rhythmbox could not understand the Magnatune catalogue, please file a bug."))
  372.                 return
  373.             out.write(catalog.read(filename))
  374.             out.close()
  375.             catalog.close()
  376.  
  377.             os.unlink(magnatune_song_info_temp)
  378.             self.__updating = False
  379.             self.__catalogue_loader = None
  380.             self.__load_catalogue()
  381.  
  382.         elif isinstance(result, Exception):
  383.             # complain
  384.             pass
  385.         else:
  386.             out.write(result)
  387.             self.__load_current_size += len(result)
  388.             self.__load_total_size = total
  389.  
  390.         self.__notify_status_changed()
  391.  
  392.  
  393.     def __download_catalogue(self):
  394.         self.__updating = True
  395.  
  396.         out = open(magnatune_song_info_temp, 'w')
  397.  
  398.         self.__catalogue_loader = rb.ChunkLoader()
  399.         self.__catalogue_loader.get_url_chunks(magnatune_song_info_uri, 4*1024, True, self.__download_catalogue_chunk_cb, out)
  400.  
  401.  
  402.     def __update_catalogue(self):
  403.         def update_cb (result):
  404.             self.__catalogue_check = None
  405.             if result is True:
  406.                 self.__download_catalogue()
  407.             elif self.__has_loaded is False:
  408.                 self.__load_catalogue()
  409.  
  410.         self.__catalogue_check = rb.UpdateCheck()
  411.         self.__catalogue_check.check_for_update(magnatune_song_info, magnatune_song_info_uri, update_cb)
  412.  
  413.  
  414.     def __show_loading_screen(self, show):
  415.         if self.__info_screen is None:
  416.             # load the glade stuff
  417.             gladexml = gtk.glade.XML(self.__plugin.find_file("magnatune-loading.glade"), root="magnatune_loading_scrolledwindow")
  418.             self.__info_screen = gladexml.get_widget("magnatune_loading_scrolledwindow")
  419.             self.pack_start(self.__info_screen)
  420.             self.get_entry_view().set_no_show_all (True)
  421.             self.__info_screen.set_no_show_all (True)
  422.  
  423.         self.__info_screen.set_property("visible", show)
  424.         self.__paned_box.set_property("visible", not show)
  425.  
  426.     def __notify_status_changed(self):
  427.         def change_idle_cb():
  428.             self.notify_status_changed()
  429.             self.__notify_id = 0
  430.             return False
  431.  
  432.         if self.__notify_id == 0:
  433.             self.__notify_id = gobject.idle_add(change_idle_cb)
  434.  
  435.     #
  436.     # internal purchasing code
  437.     #
  438.     def __buy_album(self, sku, pay, format, ccnumber, ccyear, ccmonth, name, email, gc, gc_text): # http://magnatune.com/info/api#purchase
  439.         print "purchasing tracks:", sku, pay, format, name, email
  440.         url_dict = {
  441.                         'id':    magnatune_partner_id,
  442.                         'sku':    sku,
  443.                         'amount': pay,
  444.                         'name': name,
  445.                         'email':email
  446.             }
  447.         if gc:
  448.             url_dict['gc'] = gc
  449.         else:
  450.             url_dict['cc'] = ccnumber
  451.             url_dict['yy'] = ccyear
  452.             url_dict['mm'] = ccmonth
  453.         url = "https://magnatune.com/buy/buy_dl_cc_xml?"
  454.         url = url + urllib.urlencode(url_dict)
  455.  
  456.         self.__wait_dlg = gtk.Dialog(title=_("Authorizing Purchase"), flags=gtk.DIALOG_NO_SEPARATOR|gtk.DIALOG_DESTROY_WITH_PARENT)
  457.         lbl = gtk.Label(_("Authorizing purchase with the Magnatune server. Please wait..."))
  458.         self.__wait_dlg.vbox.pack_start(lbl)
  459.         lbl.show()
  460.         self.__wait_dlg.show()
  461.  
  462.         l = rb.Loader()
  463.         l.get_url (url, self.__auth_data_cb, format)
  464.  
  465.  
  466.  
  467.     def __auth_data_cb (self, data, format):
  468.  
  469.         buy_album_handler = BuyAlbumHandler(format)
  470.         auth_parser = xml.sax.make_parser()
  471.         auth_parser.setContentHandler(buy_album_handler)
  472.  
  473.         if data is None:
  474.             # hmm.
  475.             return
  476.  
  477.         self.__wait_dlg.destroy()
  478.         try:
  479.             data = data.replace("<br>", "") # get rid of any stray <br> tags that will mess up the parser
  480.             # print data
  481.             auth_parser.feed(data)
  482.             auth_parser.close()
  483.  
  484.             # process the URI: add authentication info, quote the filename component for some reason
  485.  
  486.             parsed = urlparse.urlparse(buy_album_handler.url)
  487.             netloc = "%s:%s@%s" % (str(buy_album_handler.username), str(buy_album_handler.password), parsed.hostname)
  488.  
  489.             spath = os.path.split(urllib.url2pathname(parsed.path))
  490.             basename = spath[1]
  491.             path = urllib.pathname2url(os.path.join(spath[0], urllib.quote(basename)))
  492.  
  493.             authed = (parsed[0], netloc, path) + parsed[3:]
  494.             audio_dl_uri = urlparse.urlunparse(authed)
  495.  
  496.             in_progress = open(os.path.join(magnatune_in_progress_dir, "in_progress_" + basename), 'w')
  497.             in_progress.write(str(audio_dl_uri))
  498.             in_progress.close()
  499.  
  500.             self.__download_album(audio_dl_uri)
  501.  
  502.         except MagnatunePurchaseError, e:
  503.             rb.error_dialog(title = _("Purchase Error"),
  504.                     message = _("An error occurred while trying to purchase the album.\nThe Magnatune server returned:\n%s") % str(e))
  505.  
  506.         except Exception, e:
  507.             rb.error_dialog(title = _("Error"),
  508.                     message = _("An error occurred while trying to purchase the album.\nThe error text is:\n%s") % str(e))
  509.  
  510.  
  511.  
  512.  
  513.     def __download_album(self, audio_dl_uri):
  514.  
  515.         fullpath = urlparse.urlparse(audio_dl_uri).path
  516.         basename = os.split(fullpath)[1]
  517.         destpath = os.path.join(magnatune_in_progress_dir, basename)
  518.  
  519.         shell = self.get_property('shell')
  520.         manager = shell.get_player().get_property('ui-manager')
  521.         manager.get_action("/MagnatuneSourceViewPopup/MagnatuneCancelDownload").set_sensitive(True)
  522.         self.__downloading = True
  523.         self.cancelled = False
  524.  
  525.         self.__downloads[audio_dl_uri] = 0
  526.  
  527.         # no way to resume downloads, sadly
  528.         out = open(destpath, 'w')
  529.  
  530.         dl = rb.ChunkLoader()
  531.         dl.get_url_chunks(audio_dl_uri, 4*1024, True, self.__download_album_chunk, (audio_dl_uri, destpath, out))
  532.  
  533.  
  534.     def __remove_download_files (self, dest):
  535.         sp = os.path.split(dest)
  536.         inprogress = os.path.join(sp[0], "in_progress_" + sp[1])
  537.         os.unlink(inprogress)
  538.         os.unlink(dest)
  539.  
  540.  
  541.     def __download_finished (self, total, audio_dl_uri, dest, out):
  542.         try:
  543.             del self.__downloads[audio_dl_uri]
  544.         except:
  545.             return 0
  546.  
  547.         out.close()
  548.         self.purchase_filesize -= total
  549.  
  550.         # just use the first library location
  551.         # not quite prepared to use gio here directly yet, so we can only deal with
  552.         # local libraries here.
  553.         library_location = self.__client.get_list("/apps/rhythmbox/library_locations", gconf.VALUE_STRING)[0]
  554.         if library_location.startswith("file://"):
  555.             urlpath = urlparse.urlparse(library_location).path
  556.             library_dir = urllib.url2pathname(urlpath)
  557.         else:
  558.             library_dir = rb.music_dir ()
  559.  
  560.         album = zipfile.ZipFile(dest)
  561.         for track in album.namelist():
  562.             track_uri = "file://" + urllib.pathname2url(os.path.join(library_dir, track))
  563.  
  564.             track_uri = rb.sanitize_uri_for_filesystem(track_uri)
  565.             rb.uri_create_parent_dirs(track_uri)
  566.  
  567.             track_path = urllib.url2pathname(urlparse.urlparse(track_uri).path)
  568.  
  569.             track_out = open(track_path, 'w')
  570.             track_out.write(album.read(track))
  571.             track_out.close()
  572.  
  573.         album.close()
  574.  
  575.         self.__remove_download_files (dest)
  576.  
  577.         if self.purchase_filesize == 0:
  578.             self.__downloading = False
  579.  
  580.         self.__db.add_uri(os.path.split(track_path)[0])
  581.  
  582.  
  583.     def __download_album_chunk(self, result, total, (audio_dl_uri, dest, out)):
  584.  
  585.         if not result:
  586.             self.__download_finished (total, audio_dl_uri, dest, out)
  587.         elif isinstance(result, Exception):
  588.             # probably report this somehow?
  589.             pass
  590.         elif self.cancelled:
  591.             try:
  592.                 del self.__downloads[audio_dl_uri]
  593.                 self.purchase_filesize -= total
  594.  
  595.                 self.__remove_download_files (dest)
  596.  
  597.             except:
  598.                 pass
  599.  
  600.             if self.purchase_filesize == 0:
  601.                 self.__downloading = False
  602.  
  603.             return False
  604.         else:
  605.             if self.__downloads[audio_dl_uri] == 0:
  606.                 self.purchase_filesize += total
  607.  
  608.             out.write(result)
  609.             self.__downloads[audio_dl_uri] += len(result)
  610.  
  611.             self.__download_progress = sum(self.__downloads.values()) / float(self.purchase_filesize)
  612.             self.__notify_status_changed()
  613.  
  614.  
  615.     def cancel_downloads(self):
  616.         self.cancelled = True
  617.         shell = self.get_property('shell')
  618.         manager = shell.get_player().get_property('ui-manager')
  619.         manager.get_action("/MagnatuneSourceViewPopup/MagnatuneCancelDownload").set_sensitive(False)
  620.  
  621.     def playing_entry_changed (self, entry):
  622.         if not self.__db or not entry:
  623.             return
  624.  
  625.         if entry.get_entry_type() != self.__db.entry_type_get_by_name("MagnatuneEntryType"):
  626.             return
  627.  
  628.         gobject.idle_add (self.emit_cover_art_uri, entry)
  629.  
  630.     def emit_cover_art_uri (self, entry):
  631.         sku = self.__sku_dict[self.__db.entry_get(entry, rhythmdb.PROP_LOCATION)]
  632.         url = self.__art_dict[sku]
  633.         self.__db.emit_entry_extra_metadata_notify (entry, 'rb:coverArt-uri', url)
  634.         return False
  635.  
  636.     def __move_data_files (self):
  637.         # create cache and data directories
  638.         # (we know they don't already exist, and we know the parent dirs do)
  639.         os.mkdir(magnatune_in_progress_dir, 0700)
  640.         if os.path.exists(magnatune_cache_dir) is False:
  641.             os.mkdir(magnatune_cache_dir, 0700)
  642.  
  643.         # move song info to cache dir
  644.         old_magnatune_dir = os.path.join(rb.dot_dir(), 'magnatune')
  645.         if os.path.exists(old_magnatune_dir) is False:
  646.             print "old magnatune directory does not exist"
  647.             return
  648.  
  649.         old_song_info = os.path.join(old_magnatune_dir, 'song_info.xml')
  650.         if os.path.exists(old_song_info):
  651.             print "moving existing song_info.xml to cache dir"
  652.             os.rename(old_song_info, magnatune_song_info)
  653.         else:
  654.             print "no song_info.xml found (%s)" % old_song_info
  655.  
  656.         # move in progress downloads to data dir
  657.         otherfiles = os.listdir(old_magnatune_dir)
  658.         for f in otherfiles:
  659.             print "moving file %s to new in-progress dir" % f
  660.             os.rename(os.path.join(old_magnatune_dir, f),
  661.                   os.path.join(magnatune_in_progress_dir, f))
  662.  
  663.  
  664. gobject.type_register(MagnatuneSource)
  665.  
  666.